package avriServer.command;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import avriServer.client.NonexistantWaiterException;
import avriServer.client.Waiter;
import avriServer.client.Worker;
import avriServer.json.JSONArray;
import avriServer.json.JSONException;
import avriServer.json.JSONObject;
import avriServer.json.JSONStringer;
import avriServer.server.Dispatcher;
import avriServer.util.IdentificationException;

public abstract class Command {
	public static String ERROR_UNEXISTANT_COMMAND = "This command doesn't exist." ;
	protected JSONObject commandAttributes;
	protected HashMap<String, String> attributes = new HashMap<String, String>() ;
	private Waiter waiter ;
	private CommandBuffer resultCommands = new CommandBuffer();
	private Command parentCommand = null ;
	/**
	 * Create a new command
	 * This constructor is OBLIGATORY if you want the server automatically call this command
	 * @param _commandAttributes: all the attributes sent by the client. Usually contains the client's session, name etc...)
	 * @param _attributes: if we are working with the command: {command: "connection", attributes: {attribute1: "value1", attribute2: "value2"}}, then getCommandAttributes will return a JSONObject which contains : {attribute1: "value1", attribute2: "value2"}
	 * @throws UserDisconnectedException 
	 * @throws ErrorConnectionException 
	 */
	public Command (JSONObject _commandAttributes, HashMap<String, String> _attributes ) throws IdentificationException, NonexistantWaiterException {
		this.commandAttributes = _commandAttributes ;
		this.attributes = _attributes ;		
		//define an the waiter if it's not a SimpleCommand or a WaiterBuilder
		if (!(this instanceof WaiterBuilder) && !(this instanceof SimpleCommand)) {
			this.waiter = Dispatcher.getWaiter(_attributes.get("publicId"), _attributes.get("session"));
			//Automatically keepAlive the waiter.
			this.waiter.keepAlive();
		}
	}
	
	/**
	 * create a command from an already existing command.
	 * @param _command
	 */
	public Command (Command _command) {
		this.commandAttributes = _command.getCommandAttributes();
		this.attributes = _command.getAttributes();	
		if (!(_command instanceof SimpleCommand))
			this.waiter = _command.getWaiter();
		this.parentCommand = _command;
	}
	
	/**
	 * @return the name of the client
	 */
	public String getClientPublicId() {
		return this.getAttribute("publicId");
	}
	
	/**
	 * @return the session of the client
	 */
	public String getClientSession() {
		return this.getAttribute("session");
	}
	/**
	 * Add a specific attribute for this command.
	 * @param _attributeName
	 * @param _attributeValue
	 */
	public void addCommandAttribute(String _attributeName, String _attributeValue) {
		try {
			this.commandAttributes.accumulate(_attributeName, _attributeValue);
		} catch (JSONException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Set attributes of this command.
	 * @param _commandAttributes
	 */
	public void setCommandAttributes(JSONObject _commandAttributes) {
		this.commandAttributes = _commandAttributes;
	}

	/**
	 * Add a specific attribute for this connection.
	 * @param _attributeName
	 * @param _attributeValue
	 */
	public void addAttribute(String _attributeName, String _attributeValue) {
		this.attributes.put(_attributeName, _attributeValue);
	}

	/**
	 * set attributes of this connection.
	 * @param _attributeName
	 * @param _attributeValue
	 */
	public void setAttributes(HashMap<String, String> _attributes) {
		this.attributes = _attributes;
	}

	/**
	 * @return all the attributes sent by the client. Usually contains the client's session, name etc...
	 */
	public HashMap<String, String> getAttributes () {
		return this.attributes ;
	}
	
	/** return an attribute (e.g: the session)
	 * @param _attribute
	 * @return
	 */
	public String getAttribute(String _attribute) {
		return this.attributes.get(_attribute);
	}
	
	/**
	 * return an attribute of the command (e.g: attribute1 into the command:  {command: "connection", attributes: {attribute1: "value1", attribute2: "value2"}} 
	 * @param _commandAttribute
	 * @return the command attribute or null if the attribute doesn't exist
	 */
	public String getCommandAttribute (String _commandAttribute) {
		try {
			return this.commandAttributes.getString(_commandAttribute);
		} catch (JSONException e) {
			return null ;
		}
	}
	
	/**
	 * @return all the attributes of this command. 
	 * e.g: if we are working with the command: {command: "connection", attributes: {attribute1: "value1", attribute2: "value2"}}
	 * then getCommandAttributes will return a JSONObject which contains : {attribute1: "value1", attribute2: "value2"}
	 */
	public JSONObject getCommandAttributes () {
		return this.commandAttributes;
	}
	

	/**
	 * @return a String all the attributes of this command (as a part of JSON Array of command objects). 
	 */
	public String getResultCommands() {
		return this.resultCommands.getCommandsAsPartOfArray();
	}

	/**
	 * @return a String all the attributes of this command (as a part of JSON Array of command objects). 
	 */
	public CommandBuffer getResultCommandBuffer() {
		return this.resultCommands;
	}

	/**
	 * Set the arguments of the result of the command.
	 * e.g: if the client send the command "say", then the server will execute this command but you probably need that the server return a message to the client such as "{functionName: "info", attributes: ["Your message has been send"]}
	 * so just execute setResultCommandArguments("info", "Your message has been send") and the _result String will be send to the client before closing the connection.
	 * notice that a WaiterBuilder Command will NOT close the connection, so the ResultCommand won't be send. Use the builder.push() method to send the answer.
	 */
	public void addResultCommand (String _functionName, Object... _functionArguments) {
		if (this.parentCommand != null) {
			this.parentCommand.addResultCommand(_functionName, _functionArguments);
		}		
		this.resultCommands.append(_functionName, _functionArguments);
	}

	/**
	 * Set the arguments of the result of the command.
	 * e.g: if the client send the command "say", then the server will execute this command but you probably need that the server return a message to the client such as "{functionName: "info", attributes: ["Your message has been send"]}
	 * so just execute setResultCommandArguments("info", "Your message has been send") and the _result String will be send to the client before closing the connection.
	 * notice that a WaiterBuilder Command will NOT close the connection, so the ResultCommand won't be send. Use the builder.push() method to send the answer.
	 */
	public void addResultCommand (CommandBuffer _commandBuffer) {
		if (this.parentCommand != null) 
			this.parentCommand.addResultCommand(_commandBuffer);
		this.resultCommands.append(_commandBuffer);
	}

	/**
	 * get the current waiter. If the Command is a simple command, it will throw a SimpleCommandException, because simple command can't have a waiter.
	 */
	public Waiter getWaiter () {
		if (this instanceof SimpleCommand) {
			new SimpleCommandException().printStackTrace();
			return null;
		}
		return this.waiter;
	}
	/**
	 * Push the command to the waiter of this command.
	 * @param _functionName
	 * @param _functionAttributes
	 */
	public void push (String _functionName, Object... _functionAttributes) {
		Waiter w = this.getWaiter() ;
		if (w != null) {
			w.push(_functionName, _functionAttributes);
		}
	}
	
	/**
	 * Push the command to the waiter of this command.
	 * @param _functionName
	 * @param _functionAttributes
	 */
	public void push (String _functionName) {
		Waiter w = this.getWaiter() ;
		if (w != null) {
			w.push(_functionName);
		}
	}

	/**
	 * Push the command to the waiter of this command.
	 * @param _functionName
	 * @param _functionAttributes
	 */
	public void push (CommandBuffer _commandBuffer) {
		Waiter w = this.getWaiter() ;
		if (w != null) {
			w.push(_commandBuffer);
		}
	}

	/**
	 * set the current waiter. If the Command isn't a WaiterBuilder command, it will throw a SimpleCommandException, because a waiter can only be set by a WaiterBuilder command.
	 */
	public void setWaiter (Waiter _waiter) {
		if (!(this instanceof WaiterBuilder)) {
			new WaiterBuilderException().printStackTrace();
		}
		else {
			this.waiter = _waiter;
		}
	}

	
	public abstract void execute(Worker _w);
	
}
